package com.ksyun.campus.dataserver.util;

import com.ksyun.campus.dataserver.domain.DataServerInfo;
import lombok.Data;

import java.util.*;

@Data
public class LoadBalanceUtil {
    private List<DataServerInfo> servers;

    public LoadBalanceUtil(List<DataServerInfo> servers) {
        this.servers = servers;
    }

//    public static void main(String[] args) {
//        List<DataServerInfo> servers = new ArrayList<>();
//        servers.add(new DataServerInfo("server1", "8080", 1024, 0, "rack1", "北京"));
//        servers.add(new DataServerInfo("server2", "8080", 1024, 0, "rack2", "北京"));
//        servers.add(new DataServerInfo("server3", "8089", 1024, 0, "rack3", "北京"));
//        servers.add(new DataServerInfo("server4", "9909", 1024, 0, "rack1", "北京2"));
//        servers.add(new DataServerInfo("server5", "7890", 1024, 0, "rack2", "北京2"));
//        servers.add(new DataServerInfo("server6", "4322", 1024, 0, "rack3", "北京2"));
//
//        WeightLoadBalance loadBalancer = new WeightLoadBalance(servers);
//
//        List<DataServerInfo> results = new ArrayList<>();
//        for (int i = 0; i < 100; i++){
//            DataServerInfo s = loadBalancer.getLoadBalanceServersByFreeRatio();
//            // 模拟内存被使用
//            int use = new Random().nextInt(100);
//            if(s.getUsedCapacity()+use<s.getCapacity()){
//                s.setUsedCapacity(s.getUsedCapacity()+use);
//            }
//            results.add(s);
//        }
//
//        for (DataServerInfo s: loadBalancer.servers) {
//            System.out.println(s.getIp()+":"+ s.getPort() + "----"+ s.getCapacity()+"---"+s.getUsedCapacity()+"-----"+(100 * s.getFreeCapacityRatio())+"%");
//        }
//    }

    /**
     * 简单随机负载均衡
     *
     * @return
     */
    public DataServerInfo getRandomServer() {
        return servers.get(new Random().nextInt(servers.size()));
    }

    /**
     * 根据可使用容量比率负载均衡
     *
     * @return
     */
    public DataServerInfo getLoadBalanceServersByFreeRatio() {
        if (servers == null || servers.size() == 0) {
            return null;
        }
        servers.sort(Comparator.comparing(DataServerInfo::freeCapacityRatio).reversed());
        return servers.get(0);
    }

    /**
     * 尽可能的选不在所给机架和区域上的，不满足条件时，按可使用容量比率高的返回
     * @param num
     * @param zone
     * @param rack
     * @return
     */
    public List<DataServerInfo> getLoadBalanceServersByCondition(int num, String rack, String zone) {
        List<DataServerInfo> weightedServers = new ArrayList<>();
        if (servers != null && servers.size() <= num) {
            weightedServers.addAll(servers);
            return weightedServers;
        }

        // Sort servers by capacity ratio (higher is better)
        servers.sort(Comparator.comparing(DataServerInfo::freeCapacityRatio).reversed());

        // Select servers from different clusters and racks
        Set<String> selectedZones = new HashSet<>();
        Set<String> selectedRacks = new HashSet<>();
        if (zone!=null && zone.trim().length()!=0){
            selectedZones.add(zone);
        }
        if (rack!=null && rack.trim().length()!=0){
            selectedRacks.add(rack);
        }

        // 先按机架选
        for (DataServerInfo server : servers) {
            if (weightedServers.size() >= num) {
                break;
            }
            if (!selectedRacks.contains(server.getRack())) {
                weightedServers.add(server);
                selectedRacks.add(server.getRack());
            }
        }
        // 再按区域选
        for (DataServerInfo server : servers) {
            if (weightedServers.size() >= num) {
                break;
            }
            if (!selectedZones.contains(server.getZone()) && !selectedRacks.contains(server.getRack())) {
                weightedServers.add(server);
                selectedZones.add(server.getZone());
                selectedRacks.add(server.getRack());
            }
        }
        // 如果还不足，直接添加
        for (DataServerInfo server : servers) {
            if (weightedServers.size() >= num) {
                break;
            }
            weightedServers.add(server);
        }

        return weightedServers;
    }

    /**
     * 根据可使用容量率选择给定个数的服务器，尽量在不同机架上，不同区域， 不满足条件时， 按可使用容量比率高的返回
     *
     * @param num 需要的服务器个数
     * @return
     */
    public List<DataServerInfo> getLoadBalanceServersByFreeRatioFromDifferentDistance(int num) {
        return getLoadBalanceServersByCondition(num,null,null);
    }

//    /**
//     * 按照 可使用容量和距离 综合考虑负载均衡
//     * @return
//     */
//    public DataServerInfo getComplexWeightedServers() {
//        boolean averageWeight = true;
//        int totalWeight = 0;
//        int[] weights = calculateWeight();
//        for (int i = 0; i < servers.size(); i++) {
//            DataServerInfo server = servers.get(i);
//            if (averageWeight && i > 0 && weights[i] != weights[i - 1]) {
//                averageWeight = false;
//            }
//            totalWeight += weights[i];
//        }
//        if (averageWeight || totalWeight < 1) {
//            return getRandomServer();
//        }
//        int index = new Random().nextInt(totalWeight);
//        for (int i = 0; i < servers.size(); i++) {
//            if (index < weights[i]) {
//                return servers.get(i);
//            }
//            index -= weights[i];
//        }
//        return getRandomServer();
//
//    }

    /**
     * 计算权重： 权重 = servers.size() * distance * freeRatio
     *
     * @return
     */
    private int[] calculateWeight() {
        if (servers == null || servers.size() == 0) {
            return null;
        }
        int[] weights = new int[servers.size()];

        // Sort servers by capacity ratio (higher is better)
        servers.sort(Comparator.comparingDouble(server -> 1 - (server.getUsedCapacity() / server.getCapacity())));
        for (int i = 0; i < servers.size(); i++) {
            DataServerInfo server = servers.get(i);
            double freeRatio = 1 - (server.getUsedCapacity() / server.getCapacity());
            if (i == 0) {
                weights[i] = (int) (servers.size() * calculateDistance(servers.get(i), servers.get(i + 1)) * freeRatio);
            } else if (i == servers.size() - 1) {
                weights[i] = (int) (servers.size() * calculateDistance(servers.get(i - 1), servers.get(i)) * freeRatio);

            } else {
                weights[i] = (int) (servers.size() * (calculateDistance(servers.get(i - 1), servers.get(i)) + calculateDistance(servers.get(i), servers.get(i + 1))) / 2 * freeRatio);
            }
        }
        return weights;
    }

    /**
     * 计算服务之间距离
     *
     * @param server1
     * @param server2
     * @return
     */
    private double calculateDistance(DataServerInfo server1, DataServerInfo server2) {
        /**
         * 一台服务器的位置由集群cluster、机架rack、节点node组成，描述为某集群的某机架上的某服务节点。
         * 网络拓扑节点距离计算方法为：节点到本机架rack的距离为1，节点机架到集群的距离为1，节点与节点的距离等于它们分别到其共同祖先节点的距离之和。
         * 如：同集群同机架同节点距离=0+0=0，同集群同机架不同节点之间距离=1+1=2，同集群不同机架节点之间距离=2+2=4，不同集群之间距离=3+3=6.
         */
        double distance;
        if (!server1.getZone().equals(server2.getZone())) {
            // 不同集群节点
            distance = 0.6;
        } else if (!server1.getRack().equals(server2.getRack())) {
            // 同集群不同机架节点
            distance = 0.4;
        } else if (!(server1.getIp() + server1.getPort()).equals(server2.getIp() + server2.getPort())) {
            // 同集群同机架不同节点
            distance = 0.2;
        } else {
            // 同集群同机架同节点
            distance = 0;
        }
        return distance;
    }


}
